#include "Tossbot.h"

vec3_t vZero={0.0,0.0,0.0}; // Bot will stand still

vec3_t vFullForward={400.0,0.0,0.0}; // This will make the bot sprint forward
vec3_t vFwrt={400.0,400.0,0.0}; // Bot will run forward-right
vec3_t vRt={0.0,400.0,0.0}; // Bot will strafe right
vec3_t vBkrt={-400.0,400.0,0.0}; // Bot will run back-right
vec3_t vBk={-400.0,400.0,0.0}; // Bot will run backwards
vec3_t vBklf={-400.0,-400.0,0.0}; // Bot will run back-left
vec3_t vLf={0.0,-400.0,0.0}; // Bot will strafe left
vec3_t vFwlf={400.0,-400.0,0.0}; // Bot will run forward-left

vec3_t vCrouchForward={400.0,0.0,-400.0}; // Bot will duck-walk forward

vec3_t vJumpForward={50.0,0.0,400.0}; // This will make the bot jump while sprinting forward
vec3_t vJpfwrt={50.0,50.0,400.0}; // Bot will jump forward-right
vec3_t vJprt={0.0,50.0,400.0}; // Bot will strafe right
vec3_t vJpbkrt={-50.0,50.0,400.0}; // Bot will jump back-right
vec3_t vJpbk={-50.0,50.0,400.0}; // Bot will jump backwards
vec3_t vJpbklf={-50.0,-50.0,400.0}; // Bot will jump back-left
vec3_t vJplf={0.0,-50.0,400.0}; // Bot will jump left
vec3_t vJpfwlf={50.0,-50.0,400.0}; // Bot will jump forward-left
vec3_t vJpUp={0.0,0.0,400.0}; // Bot will jump straight up

vec3_t vJogForward={50.0,0.0,0.0}; // Bot will jog forward
vec3_t vJgfwrt={50.0,50.0,400.0}; // Bot will jog forward-right
vec3_t vJgrt={0.0,50.0,400.0}; // Bot will jog right
vec3_t vJgbkrt={-50.0,50.0,400.0}; // Bot will jog back-right
vec3_t vJgbk={-50.0,50.0,400.0}; // Bot will jog backwards
vec3_t vJgbklf={-50.0,-50.0,400.0}; // Bot will jog back-left
vec3_t vJglf={0.0,-50.0,400.0}; // Bot will jog left
vec3_t vJgfwlf={50.0,-50.0,400.0}; // Bot will jog forward-left

vec3_t dir; // This is used when calculating the direction the bot wants to face
vec3_t nextPoint; // The next waypoint in the path
vec3_t TargetPoint; // The point the bot ultimately wants to reach
vec3_t viewPoint; // The current point of interest that the bot is looking at
vec3_t lastOrientation; // This will track the bot's progress, to help him get unstuck

vec3_t botFeet; // The point at the bot's shins. Used to check if he's caught on a lip and has to jump
vec3_t botHead; // The point at the bot's head.

path_t currentPath; // An array of up to 513 vec3_t variables which the bot will follow in sequence to reach its TargetPoint
path_t weapons; // This will store locations of weapons that the bot has memorised
path_t health; // This will store locations of health packs that the bot has memorised
path_t boosters; // This will store locations of stimpacks and armour shards that the bot has memorised
path_t ammo; // This will store locations of ammo that the bot has memorised
path_t armour; // This will store locations of armour that the bot has memorised
path_t POIML; // A master list of stored points, bot will check potential new locations against this to avoid duplication

gamestate_t gs;  // Used to poll the current gamestate each frame
inventory_t inven; // Polls the bot's current inventory each frame

double presentYaw; // The bots current rotation, on a scale of 0 to 2*PI
bool Attack; // Determines whether the bot is pressing the fire key or not
bool started = false; // Checks if this is the first time the bot's loop is being called
int tryCounter = 0; // A counter used to limit the number of times the bot will attempt to calculate a path before giving up
int frameNum = 0; // This is used to limit the number of times the bot attempts to jump per frame
int pathPoint = 0; // This is used to track how far along the path the bot is
int resetCounter = 0; // This is used to limit the number of times the bot can reset a path before giving up
int Tick = 0; // Used to control how often the bot checks that it's stuck



void CheckLibAPIs()
{
	int errors = 0;
	if(qbGetAPIVersion()!=Q2BOT_API_VERSION)
	{
		Log_NewMessage("Incorrect Q2Bot.dll API version - %d not %d\r\n", qbGetAPIVersion(), Q2BOT_API_VERSION);
		errors++;
	}
	if(qmGetAPIVersion()!=Q2MAP_API_VERSION)
	{
		Log_NewMessage("Incorrect Q2Map.dll API version - %d not %d\r\n", qmGetAPIVersion(), Q2MAP_API_VERSION);
		errors++;
	}
	if(errors)
	{
		Log_NewMessage("Aborting, %d library errors\r\n", errors);
	}
	else
	{
		Log_NewMessage("Q2Bot.dll and Q2Map.dll loaded successfully\r\n");
	}
}

// This will update the location of the bot's feet
void updateBoundaries()
{
	memset(&botFeet,0,sizeof(botFeet));
	memset(&botHead,0,sizeof(botFeet));

	botFeet[0] = gs.player.origin[0];
	botFeet[1] = gs.player.origin[1];
	botFeet[2] = (gs.player.origin[2] - 16);

	botHead[0] = gs.player.origin[0];
	botHead[1] = gs.player.origin[1];
	botHead[2] = (gs.player.origin[2] + 16);
}


// resetVars()
// This function will reset everything, forcing the bot to find a new destination and path. All counters are reset too.

void resetVars()
{
	 pathPoint = 0;
	 resetCounter = 0;
	 tryCounter = 0;
 
	 memset(&currentPath, 0, sizeof(currentPath));
	 memset(&nextPoint, 0, sizeof(nextPoint));
	 memset(&TargetPoint, 0, sizeof(TargetPoint));
}

// botSay()
// Feed a char* array to this function and the bot will say it in-game

void botSay(char* string)
{
	char buf[256];
	_snprintf(buf,sizeof(buf),string,"%s");
	qbAsynchConsole(buf);
}

// This just lets the bot say a float value, I was using it to test my yaw calculations, but it can be used for anything really
void botSay(float yaw)
{
	char buf[256];
	_snprintf(buf,sizeof(buf),"%g",yaw);
	qbAsynchConsole(buf);
}



// setTargetPoint()
// Feed this function a vector and the bot will make it his destination

void setTargetPoint(vec3_t TargetVec)
{
	memset(&TargetPoint,0,sizeof(TargetPoint));
	TargetPoint[0] = TargetVec[0];
	TargetPoint[1] = TargetVec[1];
	TargetPoint[2] = TargetVec[2];
}


// setNextPoint
// Feed this function a vector and the bot will make it his next waypoint when following a path

void setNextPoint(vec3_t nextPointVect)
{
	memset(&nextPoint,0,sizeof(nextPoint));
	nextPoint[0] = nextPointVect[0];
	nextPoint[1] = nextPointVect[1];
	nextPoint[2] = nextPointVect[2];
}


// gotNextPoint
// This will return true if the bot's next waypoint is valid (i.e. not 0,0,0), or false if not

bool gotNextPoint()
{
	if ((nextPoint[0] == 0) && (nextPoint[1] == 0) && (nextPoint[2] == 0))
	{
		return false;
	}
	else
	{
		return true;
	}
}


// foundTargetPoint()
// Returns true if the bot has a valid destination (i.e. not 0,0,0), else false

bool foundTargetPoint()
{
	if ((TargetPoint[0] == 0) && (TargetPoint[1] == 0) && (TargetPoint[2] == 0))
	{
		return false;
	}
	else
	{
		return true;
	}
}


// resetPath()
// Maintains the bot's current destination, but resets his path and waypoint data, forcing him to recalculate it.
// Has an inbuilt counter to prevent infinite recalculation loops. The bot will give up completely and acquire a
// new target after 10 failed attempts

void resetPath()
{
	pathPoint = 0;
	memset(&currentPath, 0, sizeof(currentPath));
	memset(&nextPoint, 0, sizeof(nextPoint));
	resetCounter++;
	if (resetCounter == 50)
	{
		resetVars();
	}
}


// WalkToPoint()
// Will calculate the relative orientation of the supplied point compared to the bot's current orientation, and decide if
// he needs to run forward, strafe or backpedal to get to it

void WalkToPoint(vec3_t Destination)
{
	double YawFromZero;
	double relativeYaw;
	double finalResult;
	VectorSubtract(Destination, gs.player.origin, dir); // Calculate the orientation as if the bot was facing 0
	YawFromZero = normaliseYaw(yawFromVect(dir)); // Work out what yaw the bot would need to achieve to face the point if he was currently facing 0
	relativeYaw = presentYaw - YawFromZero; // Now calculate the actual yaw he needs, taking into consideration his existing yaw
	finalResult = normaliseYaw(relativeYaw); // Make sure the result is always between 0 and 2*PI

		// The yawFromVect() function always expresses orientation on a scale of 0 to 2*PI, as opposed to 360 degrees like
		// we're used to, with 0 being dead ahead and PI being directly behind. This simply tells the bot how to move depending
		// on whether his destination is in front, behind, to the side or at an angle.
			if (finalResult < 0.5 || finalResult > 5.5)
			{
				qbMovementOrder(viewPoint, vFullForward, Attack);
			}
			else
			if (finalResult < 5.5 && finalResult > 4.75)
			{
				qbMovementOrder(viewPoint, vFwlf, Attack);
			}
			else
			if (finalResult < 4.75 && finalResult > 4.25)
			{
				qbMovementOrder(viewPoint, vLf, Attack);
			}
			else
			if (finalResult < 4.25 && finalResult > 3.25)
			{
				qbMovementOrder(viewPoint, vBklf, Attack);
			}
			else
			if (finalResult < 3.25 && finalResult > 2.75)
			{
				qbMovementOrder(viewPoint, vBk, Attack);
			}
			else
			if (finalResult < 2.75 && finalResult > 1.75)
			{
				qbMovementOrder(viewPoint, vBkrt, Attack);
			}
			else
			if (finalResult < 1.75 && finalResult > 1.5)
			{
				qbMovementOrder(viewPoint, vRt, Attack);
			}
			else
			if (finalResult < 1.5 && finalResult > 0.5)
			{
				qbMovementOrder(viewPoint, vFwrt, Attack);
			}
		
		
}

void JogToPoint(vec3_t Destination)
{
	double YawFromZero;
	double relativeYaw;
	double finalResult;
	VectorSubtract(Destination, gs.player.origin, dir); // Calculate the orientation as if the bot was facing 0
	YawFromZero = normaliseYaw(yawFromVect(dir)); // Work out what yaw the bot would need to achieve to face the point if he was currently facing 0
	relativeYaw = presentYaw - YawFromZero; // Now calculate the actual yaw he needs, taking into consideration his existing yaw
	finalResult = normaliseYaw(relativeYaw); // Make sure the result is always between 0 and 2*PI

		// The yawFromVect() function always expresses orientation on a scale of 0 to 2*PI, as opposed to 360 degrees like
		// we're used to, with 0 being dead ahead and PI being directly behind. This simply tells the bot how to move depending
		// on whether his destination is in front, behind, to the side or at an angle.
		if (finalResult < 0.5 || finalResult > 5.5)
		{
			qbMovementOrder(viewPoint, vJogForward, Attack);
		}
		else
		if (finalResult < 5.5 && finalResult > 4.75)
		{
			qbMovementOrder(viewPoint, vJgfwlf, Attack);
		}
		else
		if (finalResult < 4.75 && finalResult > 4.25)
		{
			qbMovementOrder(viewPoint, vJglf, Attack);
		}
		else
		if (finalResult < 4.25 && finalResult > 3.25)
		{
			qbMovementOrder(viewPoint, vJgbklf, Attack);
		}
		else
		if (finalResult < 3.25 && finalResult > 2.75)
		{
			qbMovementOrder(viewPoint, vJgbk, Attack);
		}
		else
		if (finalResult < 2.75 && finalResult > 1.75)
		{
			qbMovementOrder(viewPoint, vJgbkrt, Attack);
		}
		else
		if (finalResult < 1.75 && finalResult > 1.5)
		{
			qbMovementOrder(viewPoint, vJgrt, Attack);
		}
		else
		if (finalResult < 1.5 && finalResult > 0.5)
		{
			qbMovementOrder(viewPoint, vJgfwrt, Attack);
		}
	
}


// JumpToPoint()
// Makes the bot turn to face the supplied point, and jump towards it. Useful if the bot is caught on a lip.

void JumpToPoint (vec3_t Destination)
{
	double YawFromZero;
	double relativeYaw;
	double finalResult;
	VectorSubtract(Destination, gs.player.origin, dir); // Calculate the orientation as if the bot was facing 0
	YawFromZero = normaliseYaw(yawFromVect(dir)); // Work out what yaw the bot would need to achieve to face the point if he was currently facing 0
	relativeYaw = presentYaw - YawFromZero; // Now calculate the actual yaw he needs, taking into consideration his existing yaw
	finalResult = normaliseYaw(relativeYaw); // Make sure the result is always between 0 and 2*PI

	if (frameNum == 0)
	{
	// The yawFromVect() function always expresses orientation on a scale of 0 to 2*PI, as opposed to 360 degrees like
	// we're used to, with 0 being dead ahead and PI being directly behind. This simply tells the bot how to move depending
	// on whether his destination is in front, behind, to the side or at an angle.
		if (finalResult < 0.5 || finalResult > 5.5)
		{
			qbMovementOrder(viewPoint, vJumpForward, Attack);
		}
		else
		if (finalResult < 5.5 && finalResult > 4.75)
		{
			qbMovementOrder(viewPoint, vJpfwlf, Attack);
		}
		else
		if (finalResult < 4.75 && finalResult > 4.25)
		{
			qbMovementOrder(viewPoint, vJplf, Attack);
		}
		else
		if (finalResult < 4.25 && finalResult > 3.25)
		{
			qbMovementOrder(viewPoint, vJpbklf, Attack);
		}
		else
		if (finalResult < 3.25 && finalResult > 2.75)
		{
			qbMovementOrder(viewPoint, vJpbk, Attack);
		}
		else
		if (finalResult < 2.75 && finalResult > 1.75)
		{
			qbMovementOrder(viewPoint, vJpbkrt, Attack);
		}
		else
		if (finalResult < 1.75 && finalResult > 1.5)
		{
			qbMovementOrder(viewPoint, vJprt, Attack);
		}
		else
		if (finalResult < 1.5 && finalResult > 0.5)
		{
			qbMovementOrder(viewPoint, vJpfwrt, Attack);
		}
	}
	else
	{
		WalkToPoint(nextPoint);
	}
}


// validateNextPoint()
// Picks the most appropriate next waypoint for the bot to follow, ensuring that it isn't too close to its current point,
// is directly visible and isn't further away than the next one in the path.

void validateNextPoint()
{
	setNextPoint(currentPath.points[pathPoint]);

	if ((distFromVect(gs.player.origin, nextPoint) > 128.0) || qmTraceLine(botHead, nextPoint, CONTENTS_SOLID|CONTENTS_WINDOW) < 1.0)
	{
		resetPath();
	}
	else
	{
		// First check that the next waypoint isn't already too close and therefore pointless, and if so then pick the next one
		if (distFromVect(gs.player.origin, nextPoint) < 56.0)
		{
			pathPoint++;
			setNextPoint(currentPath.points[pathPoint]);
		}
		// Now check to make sure that the next point isn't closer than the current one (useful if the bot is running down
		// stairs and overshoots a waypoint, as it will prevent him running back up the steps to touch it unnecessarily)
		if ((distFromVect(gs.player.origin, nextPoint) > distFromVect(gs.player.origin, currentPath.points[pathPoint+1])) && (jumpable(gs.player.origin, currentPath.points[pathPoint+1], true) == true))
		{
			pathPoint++;
			setNextPoint(currentPath.points[pathPoint]);
		} 
	}
}


// hasPath()
// Checks that the bot has a path calculated to reach his current destination

bool hasPath()
{
	// Quite a simple solution. If the second point in the bot's path is at 0,0,0 then currentPath must be empty
	if(currentPath.points[2][0] == 0 && currentPath.points[2][1] == 0 && currentPath.points[2][2] == 0)
	{
		return false;
	}
	else
	{
		return true;
	}
}


// getTarget()
// The bot will pick a random entity from all the ones in the local vicinity. If it has a model (i.e. is an item of some kind)
// then make that the bot's destination, otherwise just keep trying

void getTarget()
{
	qbMovementOrder(vZero, vZero, false);
	if (gs.player.health < 60)
	{
		setTargetPoint(health.points[randomGen(health)]);
	}
	else
	{
		int randomChoice = rand() % 3;

		if (randomChoice == 0)
		{
			if (POIsize(weapons) > 0)
			{
				setTargetPoint(weapons.points[randomGen(weapons)]);
			}
		}
		else if (randomChoice == 1)
		{
			if (POIsize(armour) > 0)
			{
				setTargetPoint(armour.points[randomGen(armour)]);
			}
		}
		else if (randomChoice == 2)
		{
			if (POIsize(ammo) > 0)
			{
				setTargetPoint(ammo.points[randomGen(ammo)]);
			}
		}
	}
}


// calcPath()
// Tries to calculate a path to the bot's current destination (TargetPoint). It will try 50 times then give up and reset.

void calcPath()
{

	memset(&currentPath,0,sizeof(currentPath));

	if (frameNum == 0)
	{
		unStuck();
	}
	else
	{
		qbMovementOrder(vZero, vZero, false);
	}

	if (qmFindPath(gs.player.origin, TargetPoint, &currentPath, true) == true)
	{
		tryCounter = 0;
	}
	else
	{
		tryCounter++;
	}
	if (tryCounter == 10)
	{
		resetVars();
	}
}

// getFrameNo()
// A simple (very crude) way of limiting the number of times the bot will try to perform an action. This is to prevent Quake 2
// thinking that the bot is holding down a button if he needs to be tapping it (like jumping)

void getFrameNo()
{
	if (frameNum < 5)
	{
		frameNum++;
	}
	else
	{
		frameNum = 0;
		Tick++;
		if (Tick > 5)
		{
			Tick = 0;
			memset(&lastOrientation,0,sizeof(lastOrientation));
			lastOrientation[0] = gs.player.origin[0];
			lastOrientation[1] = gs.player.origin[1];
			lastOrientation[2] = gs.player.origin[2];
		}
	}
}

// Calculates where the bot wants to look at a particular moment in time. If there is an enemy in the vicinity then look at
// him and attack, otherwise look ahead in the path, up to 20 waypoints ahead as long as they are within line of sight.
void calcViewPoint()
{
	memset(&viewPoint,0,sizeof(viewPoint));
	vec3_t viewTarget;
	int Target = 0;
	int pathView = pathPoint;

		for (int i = 0; i < gs.max_players; i++)
		{
			if(!gs.entities[i].modelindex)
				continue;
			if(i == gs.player_entity)
				continue;
			if(gs.player.health <= 0)
				continue;

			float frac = qmTraceLine(gs.player.origin, gs.entities[i].origin, CONTENTS_SOLID|CONTENTS_WINDOW);
			if (frac == 1.0) // If the beam reaches its target
			{
				Target = i;
				VectorSubtract(gs.entities[i].origin, gs.player.origin, dir); // Calculate the orientation so the bot can face it
				viewPoint[0] = pitchFromVect(dir);
				viewPoint[1] = normaliseYaw(yawFromVect(dir));
				presentYaw = normaliseYaw(yawFromVect(dir));
				Attack = true;
			}
		}

		if (Target == 0)
		{
			Attack = false;
			if(hasPath() == true)
			{
				int counter = 0;
				while((qmTraceLine(gs.player.origin, currentPath.points[pathView], CONTENTS_SOLID|CONTENTS_WINDOW) == 1.0) && counter < 20)
				{
					pathView++;
					memset(&viewTarget,0,sizeof(viewTarget));
					viewTarget[0] = currentPath.points[pathView][0];
					viewTarget[1] = currentPath.points[pathView][1];
					viewTarget[2] = currentPath.points[pathView][2] + 4;
					counter++;
				}
				VectorSubtract(viewTarget, gs.player.origin, dir);
				viewPoint[0] = pitchFromVect(dir);
				viewPoint[1] = normaliseYaw(yawFromVect(dir));
				presentYaw = normaliseYaw(yawFromVect(dir));
			}
		}
	
}

// This makes the bot run directly to a point, ignoring viewPoints and anything else
void RunDirect(vec3_t Destination)
{
	vec3_t angles;
	VectorSubtract(Destination, gs.player.origin, dir);
	angles[0] = pitchFromVect(dir);
	angles[1] = yawFromVect(dir);
	qbMovementOrder(angles, vFullForward, false);
}


// If the bot has a valid destination and a path calculated, return true, otherwise false
bool hasGoal()
{
	if (foundTargetPoint() == true && hasPath() == true)
	{
		return true;
	}
	else
	{
		return false;
	}
}

// The main bot loop
void botLoop()
{
	
	qbGetGameState(&gs);
	qbGetInventory(&inven);

	updateBoundaries();
	getFrameNo(); // This is a counter that loops from 0 to 4 every time botLoop() is called
	scanArea();
	calcViewPoint();
	
	// Temporary: If the bot is in water, kill himself since the bot can't swim and just drowns anyway. TO DO: Swimming
	if (qmLeafContents(qmGetBSPLeaf(gs.player.origin)) == 32)
	{
		botSay("kill");
	}
	// If the bot is dead then shoot to respawn
	if (gs.player.health < 1)
	{
		qbMovementOrder(vZero, vZero, true);
		resetVars();
	}
	// Check if the bot is stuck, and try to unstick him if he is
	else if (isStuck() == true)
	{
		unStuck();
	}
	// If the bot has a destination and path calculated
	else if (hasGoal() == true)
	{
		nextMove();
	}
	// If the bot has no destination or path, then get one
	else
	{
		if (foundTargetPoint() == false)
		{
			getTarget();
		}
		else
		{
			calcPath();
		}
	}
}



void CreateBot()
{
	CheckLibAPIs();
	
	if(!qbStartup(Log_PrintN,Log_PrintN))
	{
		Log_Print("Q2BotCore Startup Failed!\r\n");
		return;
	}

	// TODO: GUI for name of demo and whether or not to record
	qbRecordDemo("debug.dm2");
	
	// extra q2map integration stuff - listed as alpha
	if(useTraceFunc)
	{
		qbSetTraceLineFunc(qmTraceLine);
		Log_NewMessage("Exporting qmTraceLine to q2bot...\r\n");
	}
	if(useBrushFunc)
	{
		qmSetBrushLocationFunc(qbGetBrushLocation);
		Log_NewMessage("Exporting qbGetBrushLocation to q2map...\r\n");
	}

	// TODO: userinfo
	qbUpdateUserinfo("name",botname);

	Log_NewMessage("Connecting to %s:%d",hostname,port);
	if(!qbConnect(hostname,port))
	{
		Log_Print("Connection Failed!\r\n");
		return;
	}
	Log_Print("Connected! Initialising...\r\n");

	char mapname[64];
	if(!qbGetModelString(1,mapname))
	{
		Log_Print("Error: no map\r\n"); // shouldn't happen
		return;
	}

	// TODO: make gamedir configurable.
	if(!qmLoadMap(quakeDir,"baseq2",mapname,Log_Print,Log_Print))
	{
		Log_Print("Map failed to load!\r\n");
		return;
	}

	qbBegin();

	clock_t nextFrameTime = 0;
	int fpsInterval; // time between frames, in ms

	fpsInterval = CLOCKS_PER_SEC / tickRate;
	
	resetVars();

	memset(&weapons,0,sizeof(weapons));
	memset(&health,0,sizeof(health));
	memset(&armour,0,sizeof(armour));
	Attack = false;

	// main loop
	while(qbIsConnected())
	{
		clock_t msClock;
		msClock = clock();

		if(msClock < nextFrameTime)
		{
			Sleep(1); // necessary?
			continue;
		}

		nextFrameTime = msClock + fpsInterval; // goes before frame to make system run full pace if behind		
		
		botLoop();

		DoStats();
	}

	DoStats();

	Log_NewMessage("Q2Bot.dll reported disconnect.\r\n");
}


// Returns the distance between two points in space
float distFromVect(vec3_t u,vec3_t v) {
	float x,y,z;

	x=v[0]-u[0];
	y=v[1]-u[1];
	z=v[2]-u[2];
	return (float)sqrt(x*x+y*y+z*z);
}


// The WalkToPoint function relies on the relative yaw being a value between 0 and 2*PI. This just makes sure that whatever
// yaw is calculated will always be in that range. Easy peasy.
double normaliseYaw(double currentYaw)
{
	double result = currentYaw;

	while (result < 0.0)
	{
		result = result + 2*PI;
	}

	while (result > 2*PI)
	{
		result = result - 2*PI;
	}

	return result;
}


// scanArea()
// This function will gather information on all nearby points of interest (weapons, health, armour etc) and store it
// for future reference so the bot can return there when it needs a weapon, health or ammo.

void scanArea()
{
	char weaponName[16] = "models/weapons/";
	char healthName[22] = "models/items/healing/";
	char armourName[20] = "models/items/armor/";
	char ammoName[19] = "models/items/ammo/";
	char weaponMatch[16];
	char healthMatch[22];
	char armourMatch[20];
	char ammoMatch[19];
	memset(&weaponMatch,0,sizeof(weaponMatch));
	memset(&healthMatch,0,sizeof(healthMatch));
	memset(&armourMatch,0,sizeof(armourMatch));
	memset(&ammoMatch,0,sizeof(ammoMatch));

	for (int i = (gs.max_players + 1); i < (1023 - gs.max_players); i++)
	{
		if(!gs.entities[i].modelindex)
		{
			continue;
		}
		if(gs.player.health <= 0)
		{
			continue;
		}

		char modelName[256];
		qbGetModelString(i,modelName);

		modelSubstring(modelName, weaponMatch, 15);
		modelSubstring(modelName, healthMatch, 21);
		modelSubstring(modelName, armourMatch, 19);
		modelSubstring(modelName, ammoMatch, 18);

		if(strcmp(weaponName, weaponMatch) == 0)
		{
			addWeaponPoint(gs.entities[i].origin);
		}
		else if(strcmp(healthName, healthMatch) == 0)
		{
			addHealthPoint(gs.entities[i].origin);
		}
		else if(strcmp(armourName, armourMatch) == 0)
		{
			addArmourPoint(gs.entities[i].origin);
		}
		else if(strcmp(ammoName, ammoMatch) == 0)
		{
			addAmmoPoint(gs.entities[i].origin);
		}
	}
}

// Adds the vector to the bot's list of weapon locations. The bot by default will pick one of these as his destination if
// he doesn't need health, armour or ammo
void addWeaponPoint(vec3_t POI)
{
	int match = -1;
	int endPoint = -1;

	for (int i = 0; i < 513; i++)
	{
		if (POIML.points[i][0] == POI[0] && POIML.points[i][1] == POI[1] && POIML.points[i][2] == POI[2])
		{
			match = i;
		}
	}

	if (match < 0)
	{
		for (int i = 0; i < 513; i++)
		{
			if (weapons.points[i][0] == 0 && weapons.points[i][1] == 0 && weapons.points[i][2] == 0)
			{
				endPoint = i;
				break;
			}
		}
	}

	if (endPoint > -1)
	{
		vec3_t testHeight;
		testHeight[0] = POI[0];
		testHeight[1] = POI[1];
		testHeight[2] = (POI[2]-24);

		weapons.points[endPoint][0] = POI[0];
		weapons.points[endPoint][1] = POI[1];

		float frac = qmTraceLine(POI, testHeight, CONTENTS_SOLID|CONTENTS_WINDOW);
		if (frac < 1.0)
		{
			weapons.points[endPoint][2] = (POI[2] + (24*(1.0F - frac)));
		}
		else
		{
			weapons.points[endPoint][2] = POI[2];
		}
		markPoint(POI);
	}
}

// Adds the vector to the bot's list of health points. The bot may choose to visit that location if he needs health
// Has inbuilt checks to avoid duplicate entries
void addHealthPoint(vec3_t POI)
{
	int match = -1;
	int endPoint = -1;

	for (int i = 0; i < 513; i++)
	{
		if (POIML.points[i][0] == POI[0] && POIML.points[i][1] == POI[1] && POIML.points[i][2] == POI[2])
		{
			match = i;
		}
	}

	if (match < 0)
	{
		for (int i = 0; i < 513; i++)
		{
			if (health.points[i][0] == 0 && health.points[i][1] == 0 && health.points[i][2] == 0)
			{
				endPoint = i;
				break;
			}
		}
	}

	if (endPoint > -1)
	{
		vec3_t testHeight;
		testHeight[0] = POI[0];
		testHeight[1] = POI[1];
		testHeight[2] = (POI[2]-24);

		health.points[endPoint][0] = POI[0];
		health.points[endPoint][1] = POI[1];

		float frac = qmTraceLine(POI, testHeight, CONTENTS_SOLID|CONTENTS_WINDOW);
		if (frac < 1.0)
		{
			health.points[endPoint][2] = (POI[2] + (24*(1.0F - frac)));
		}
		else
		{
			health.points[endPoint][2] = POI[2];
		}
		markPoint(POI);
	}

}

// Adds the vector to the bot's list of armour points. The bot may choose to visit that location if he needs armour
// Has inbuilt checks to avoid duplicate entries
void addArmourPoint(vec3_t POI)
{
	int match = -1;
	int endPoint = -1;

	for (int i = 0; i < 513; i++)
	{
		if (POIML.points[i][0] == POI[0] && POIML.points[i][1] == POI[1] && POIML.points[i][2] == POI[2])
		{
			match = i;
		}
	}

	if (match < 0)
	{
		for (int i = 0; i < 513; i++)
		{
			if (armour.points[i][0] == 0 && armour.points[i][1] == 0 && armour.points[i][2] == 0)
			{
				endPoint = i;
				break;
			}
		}
	}

	if (endPoint > -1)
	{
		vec3_t testHeight;
		testHeight[0] = POI[0];
		testHeight[1] = POI[1];
		testHeight[2] = (POI[2]-24);

		armour.points[endPoint][0] = POI[0];
		armour.points[endPoint][1] = POI[1];

		float frac = qmTraceLine(POI, testHeight, CONTENTS_SOLID|CONTENTS_WINDOW);
		if (frac < 1.0)
		{
			armour.points[endPoint][2] = (POI[2] + (24*(1.0F - frac)));
		}
		else
		{
			armour.points[endPoint][2] = POI[2];
		}
		markPoint(POI);
	}

}

// Adds the vector to the bot's list of ammo points. The bot may choose to visit that location if he needs ammo
// Has inbuilt checks to avoid duplicate entries
void addAmmoPoint(vec3_t POI)
{
	int match = -1;
	int endPoint = -1;

	for (int i = 0; i < 513; i++)
	{
		if (POIML.points[i][0] == POI[0] && POIML.points[i][1] == POI[1] && POIML.points[i][2] == POI[2])
		{
			match = i;
		}
	}

	if (match < 0)
	{
		for (int i = 0; i < 513; i++)
		{
			if (ammo.points[i][0] == 0 && ammo.points[i][1] == 0 && ammo.points[i][2] == 0)
			{
				endPoint = i;
				break;
			}
		}
	}

	if (endPoint > -1)
	{
		vec3_t testHeight;
		testHeight[0] = POI[0];
		testHeight[1] = POI[1];
		testHeight[2] = (POI[2]-24);

		ammo.points[endPoint][0] = POI[0];
		ammo.points[endPoint][1] = POI[1];

		float frac = qmTraceLine(POI, testHeight, CONTENTS_SOLID|CONTENTS_WINDOW);
		if (frac < 1.0)
		{
			ammo.points[endPoint][2] = (POI[2] + (24*(1.0F - frac)));
		}
		else
		{
			ammo.points[endPoint][2] = POI[2];
		}
		markPoint(POI);
	}

}

// Returns a substring of modelName of length length, and puts it into target. Useful if you want the bot to pick up
// certain categories of model and not specific models
void modelSubstring(char* modelName, char* target, int length)
{
	for (int i = 0; i < length; i++)
	{
		target[i] = modelName[i];
	}
}

// Returns a random, valid integer based on the size of the bot's list of known points of interest. Generates 5 random
// integers then picks one of the 5 at random. Should help the bot be more varied in his selection of destinations
int randomGen(path_t POIs)
{
	int sample[5];

	for (int i = 0; i < 5; i++)
	{
		sample[i] = rand() % POIsize(POIs);
	}

	return sample[rand() % 4];
}

// Each point of interest list is up to 513 points long, but not all of them will be filled. This returns the number of filled
// points the bot currently has for the selected POI list. Used in conjunction with randomGen()
int POIsize(path_t poiArray)
{
	for (int i = 0; i < 513; i++)
	{
		if (weapons.points[i][0] == 0 && weapons.points[i][1] == 0 && weapons.points[i][2] == 0)
		{
			return i;
		}
	}
}


// This does one of two things depending on needsJump. If needsJump is false, it will check to see if the bot needs to jump
// to his next destination by checking the height difference between his current location and the destination. If needsJump
// is true, the routine assumes that the bot has already determined that he needs to jump, and it will check to see if the
// next point is too high and out of reach instead
bool jumpable(vec3_t from, vec3_t to, bool needsJump)
{
	float result;
	float a = from[2];
	float b = to[2];
	a += 1000.0F;
	b += 1000.0F;
	result = b - a;
	if (needsJump == true)
	{
		if (result <= 42.0F)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	else if (needsJump == false)
	{
		if (result <= 12.0F || (qmTraceLine(botFeet, nextPoint, CONTENTS_SOLID|CONTENTS_WINDOW) == 1.0) || (bigDrop(gs.player.origin, nextPoint) == true))
		{
			return false;
		}
		else
		{
			return true;
		}		
	}
}

// Checks to see if the bot is jumping off a ledge. If the next waypoint is more than 32 units below his current height,
// return true. Used in conjunction with JogToPoint to make the bot walk off ledges rather than hurl himself off at top speed
bool bigDrop(vec3_t from, vec3_t to)
{
	float result;
	float a = from[2];
	float b = to[2];
	a += 1000.0F;
	b += 1000.0F;
	result = b - a;
	if (result < -32.0F)
	{
		return true;
	}
	else
	{
		return false;
	}
}

// If the bot has moved less than 4 units since his last check then he is stuck, otherwise he's ok
bool isStuck()
{
	if ((distFromVect(lastOrientation, gs.player.origin) < 4.0) && (bigDrop(gs.player.origin, nextPoint) == false) && (frameNum == 4))
	{
		return true;
	}
	else
	{
		return false;
	}		
}




// Will make the bot randomly move in a direction. Useful for unsticking him from corners or helping him calculate a path.
void unStuck()
{
	if (gs.player.health < 1)
	{
		qbMovementOrder(vZero,vZero,true);
		resetVars();
	}

	int randomMove = rand() % 4;
	
	if (randomMove == 0)
	{
		qbMovementOrder(vZero, vFullForward, false);
	}
		else if (randomMove == 1)
	{
		qbMovementOrder(vZero, vLf, false);
	}
	else if (randomMove == 2)
	{
		qbMovementOrder(vZero, vRt, false);
	}
	else if (randomMove == 3)
	{
		qbMovementOrder(vZero, vBk, false);
	}
}

// This function calculates what the bot's next move should be in terms of how fast, what direction, jumping etc.
void nextMove()
{
	if (gs.player.health < 1)
	{
		qbMovementOrder(vZero,vZero,true);
	}
	else
	{		
		if ((distFromVect(gs.player.origin, TargetPoint) > 128.0) || (qmTraceLine(gs.player.origin, TargetPoint, CONTENTS_SOLID|CONTENTS_WINDOW) < 1.0))
		{
			validateNextPoint(); // Work out the best point in the path to head for next
			if (bigDrop(gs.player.origin, nextPoint) == true) // If the bot is dropping off a high ledge
			{
				JogToPoint(nextPoint); // Will make him inch off the ledge rather than hurl himself like a lemming
			}
			else if (jumpable(gs.player.origin, nextPoint, false) == true) // If the bot needs to jump to his next point
			{
				if (jumpable(gs.player.origin, nextPoint, true) == true) // If the bot is able to reach his next point
				{
					JumpToPoint(nextPoint);
				}
				else
				{
					resetPath();
				}
			}
			else
			{
				WalkToPoint(nextPoint); // If he doesn't need to jump then just have him run instead
			}
		}
		else
		{
			if (reachedDestination(TargetPoint) == false)
			{
				RunDirect(TargetPoint); // Forget everything else, just run for home!
			}
			else
			{
				resetVars();
			}
		}
	}
}


// Occasionally there is an issue with the bot thinking his destination is elevated in the air, causing him to jump around
// like mad. This will tell him to ignore the distance along the Z-axis. Since this function is only called once the bot
// gets within 128 units of a destination, it's unlikely he'll need to worry about the Z-axis anyway
bool reachedDestination(vec3_t destination)
{
	if ((((gs.player.origin[0] - destination[0]) > -16) && ((gs.player.origin[0] - destination[0]) < 16)) && (((gs.player.origin[1] - destination[1]) > -16) && ((gs.player.origin[1] - destination[1]) < 16)))
	{
		return true;
	}
	else
	{
		return false;
	}
}

// Marks a point on the bot's known list of POIs. When scanArea() is called, the bot will check found POIs against this list
// and ignore them if he already knows about them
void markPoint(vec3_t POI)
{
	for (int i = 0; i < 513; i++)
	{
		if ((POIML.points[i][0] == 0) && (POIML.points[i][1] == 0) && (POIML.points[i][2] == 0))
		{
			POIML.points[i][0] = POI[0];
			POIML.points[i][1] = POI[1];
			POIML.points[i][2] = POI[2];
			break;
		}
	}
}